/*
 * Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
 *
 * This file is part of libtiled-java.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package mh.map;

import java.awt.Color;
import java.awt.Image;
import java.awt.Rectangle;
import java.io.IOException;
import java.util.Iterator;
import java.util.Vector;

/**
 * todo: Update documentation
 * <p>
 * TileSet handles operations on tiles as a set, or group. It has several advanced internal functions aimed at reducing
 * unnecessary data replication. A 'tile' is represented internally as two distinct pieces of data. The first and most
 * important is a {@link Tile} object, and these are held in a {@link Vector}.
 * </p>
 * 
 * <p>
 * The other is the tile image.
 * </p>
 */
public class TileSet implements Iterable<Tile> {
	private String				externalSource;
	private String				name;
	private String				imgSource;
	private Rectangle			tileDimensions;
	private int					tileSpacing;
	private int					tileMargin;

	private String				base;
	final private Vector<Tile>	tiles	= new Vector<Tile>();
	private int					tilesPerRow;
	//	private File				tilebmpFile;
	private Color				transparentColor;
	private Image				tileSetImage;

	protected Texture			texture;

	/**
	 * Default constructor
	 */
	public TileSet() {
		this.tileDimensions = new Rectangle();
	}

	/**
	 * Create a tileset from the image file. The texture is stored in the <code>TileSet</code> and a reference of it is
	 * stored in each <code>Tile</code>. The texture stay whole in all the process, no cuts are made.
	 * 
	 * @param ref
	 * @param tileWidth
	 * @param tileHeight
	 * @param tileSpacing
	 * @param tileMargin
	 */
	public void importTileTexture(String ref, int tileWidth, int tileHeight, int tileSpacing, int tileMargin) {
		try {
			this.tileDimensions.width = tileWidth;
			this.tileDimensions.height = tileHeight;
			this.tileSpacing = tileSpacing;
			this.tileMargin = tileMargin;
			this.texture = TextureLoader.getTexture(ref, true, null);
			int texWidth = this.texture.getImageWidth();
			int texHeight = this.texture.getImageHeight();
			int tilePerRow = texWidth / tileWidth;
			int rows = texHeight / tileHeight;
			for (int i = 0; i < rows; i++) {
				for (int j = 0; j < tilePerRow; j++) {
					Tile t = new Tile(this, j, i);
					this.addNewTile(t);
				}
			}

		} catch (IOException e) {
			System.err.println("Unable to load texture: " + ref);
			e.printStackTrace();
		}
	}

	//	/**
	//	 * Creates a tileset from a tileset image file.
	//	 * 
	//	 * @param imgFilename
	//	 * @param cutter
	//	 * @throws IOException
	//	 * @see TileSet#importTileBitmap(BufferedImage, TileCutter)
	//	 */
	//	public void importTileBitmap(String imgFilename, TileCutter cutter) throws IOException {
	//		setTilesetImageFilename(imgFilename);
	//
	//		Image image = ImageIO.read(new File(imgFilename));
	//		if (image == null) {
	//			throw new IOException("Failed to load " + tilebmpFile);
	//		}
	//
	//		Toolkit tk = Toolkit.getDefaultToolkit();
	//
	//		if (transparentColor != null) {
	//			int rgb = transparentColor.getRGB();
	//			image = tk.createImage(new FilteredImageSource(image.getSource(), new TransparentImageFilter(rgb)));
	//		}
	//
	//		BufferedImage buffered = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
	//		buffered.getGraphics().drawImage(image, 0, 0, null);
	//
	//		importTileBitmap(buffered, cutter);
	//	}
	//
	//	/**
	//	 * Creates a tileset from a buffered image. Tiles are cut by the passed cutter.
	//	 * 
	//	 * @param tileBitmap
	//	 *            the image to be used, must not be null
	//	 * @param cutter
	//	 *            the tile cutter, must not be null
	//	 */
	//	private void importTileBitmap(BufferedImage tileBitmap, TileCutter cutter) {
	//		assert tileBitmap != null;
	//		assert cutter != null;
	//
	//		tileCutter = cutter;
	//		tileSetImage = tileBitmap;
	//
	//		cutter.setImage(tileBitmap);
	//
	//		tileDimensions = new Rectangle(cutter.getTileDimensions());
	//		if (cutter instanceof BasicTileCutter) {
	//			BasicTileCutter basicTileCutter = (BasicTileCutter) cutter;
	//			tileSpacing = basicTileCutter.getTileSpacing();
	//			tileMargin = basicTileCutter.getTileMargin();
	//			tilesPerRow = basicTileCutter.getTilesPerRow();
	//		}
	//
	//		Image tileImage = cutter.getNextTile();
	//		while (tileImage != null) {
	//			Tile tile = new Tile(this);
	//							tile.setImage(tileImage);
	//			addNewTile(tile);
	//			tileImage = cutter.getNextTile();
	//		}
	//	}

	//	/**
	//	 * Refreshes a tileset from a tileset image file.
	//	 * 
	//	 * @throws IOException
	//	 * @see TileSet#importTileBitmap(BufferedImage,TileCutter)
	//	 */
	//	private void refreshImportedTileBitmap() throws IOException {
	//		String imgFilename = tilebmpFile.getPath();
	//
	//		Image image = ImageIO.read(new File(imgFilename));
	//		if (image == null) {
	//			throw new IOException("Failed to load " + tilebmpFile);
	//		}
	//
	//		Toolkit tk = Toolkit.getDefaultToolkit();
	//
	//		if (transparentColor != null) {
	//			int rgb = transparentColor.getRGB();
	//			image = tk.createImage(new FilteredImageSource(image.getSource(), new TransparentImageFilter(rgb)));
	//		}
	//
	//		BufferedImage buffered = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
	//		buffered.getGraphics().drawImage(image, 0, 0, null);
	//
	//		refreshImportedTileBitmap(buffered);
	//	}

	//	/**
	//	 * Refreshes a tileset from a buffered image. Tiles are cut by the passed cutter.
	//	 * 
	//	 * @param tileBitmap
	//	 *            the image to be used, must not be null
	//	 */
	//	private void refreshImportedTileBitmap(BufferedImage tileBitmap) {
	//		assert tileBitmap != null;
	//
	//		tileCutter.reset();
	//		tileCutter.setImage(tileBitmap);
	//
	//		tileSetImage = tileBitmap;
	//		tileDimensions = new Rectangle(tileCutter.getTileDimensions());
	//
	//		int id = 0;
	//		Image tileImage = tileCutter.getNextTile();
	//		while (tileImage != null) {
	//			Tile tile = getTile(id);
	//			tile.setImage(tileImage);
	//			tileImage = tileCutter.getNextTile();
	//			id++;
	//		}
	//	}

	//	public void checkUpdate() throws IOException {
	//		if (tilebmpFile != null && tilebmpFile.lastModified() > tilebmpFileLastModified) {
	//			refreshImportedTileBitmap();
	//			tilebmpFileLastModified = tilebmpFile.lastModified();
	//		}
	//	}

	/**
	 * Sets the URI path of the external source of this tile set. By setting this, the set is implied to be external in
	 * all other operations.
	 * 
	 * @param source
	 *            a URI of the tileset image file
	 */
	public void setSource(String source) {
		this.externalSource = source;
	}

	/**
	 * Sets the base directory for the tileset
	 * 
	 * @param base
	 *            a String containing the native format directory
	 */
	public void setBaseDir(String base) {
		this.base = base;
	}

	/**
	 * Sets the name of this tileset.
	 * 
	 * @param name
	 *            the new name for this tileset
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the name of this tileset.
	 */
	public String getName() {
		return this.name;
	}

	/**
	 * Sets the transparent color in the tileset image.
	 * 
	 * @param color
	 */
	public void setTransparentColor(Color color) {
		this.transparentColor = color;
	}

	/**
	 * Adds the tile to the set, setting the id of the tile only if the current value of id is -1.
	 * 
	 * @param t
	 *            the tile to add
	 * @return int The <b>local</b> id of the tile
	 */
	public int addTile(Tile t) {
		if (t.getId() < 0) {
			t.setId(this.tiles.size());
		}

		//TODO WHAT IS THIS?
		if (this.tileDimensions.width < t.getWidth()) {
			this.tileDimensions.width = t.getWidth();
		}

		if (this.tileDimensions.height < t.getHeight()) {
			this.tileDimensions.height = t.getHeight();
		}

		this.tiles.add(t);
		t.setTileSet(this);

		return t.getId();
	}

	/**
	 * This method takes a new Tile object as argument, and in addition to the functionality of <code>addTile()</code>,
	 * sets the id of the tile to -1.
	 * 
	 * @see TileSet#addTile(Tile)
	 * @param t
	 *            the new tile to add.
	 */
	public void addNewTile(Tile t) {
		t.setId(-1);
		this.addTile(t);
	}

	/**
	 * Removes a tile from this tileset. Does not invalidate other tile indices. Removal is simply setting the reference
	 * at the specified index to <b>null</b>.
	 * 
	 * @param i
	 *            the index to remove
	 */
	public void removeTile(int i) {
		this.tiles.set(i, null);
	}

	/**
	 * Returns the amount of tiles in this tileset.
	 * 
	 * @return the amount of tiles in this tileset
	 */
	public int size() {
		return this.tiles.size();
	}

	/**
	 * Returns the maximum tile id.
	 * 
	 * @return the maximum tile id, or -1 when there are no tiles
	 */
	public int getMaxTileId() {
		return this.tiles.size() - 1;
	}

	/**
	 * Returns an iterator over the tiles in this tileset.
	 * 
	 * @return an iterator over the tiles in this tileset.
	 */
	@Override
	public Iterator<Tile> iterator() {
		return this.tiles.iterator();
	}

	/**
	 * Returns the width of tiles in this tileset. All tiles in a tileset should be the same width, and the same as the
	 * tile width of the map the tileset is used with.
	 * 
	 * @return int - The maximum tile width
	 */
	public int getTileWidth() {
		return this.tileDimensions.width;
	}

	/**
	 * Returns the tile height of tiles in this tileset. Not all tiles in a tileset are required to have the same
	 * height, but the height should be at least the tile height of the map the tileset is used with.
	 * 
	 * If there are tiles with varying heights in this tileset, the returned height will be the maximum.
	 * 
	 * @return the max height of the tiles in the set
	 */
	public int getTileHeight() {
		return this.tileDimensions.height;
	}

	/**
	 * Returns the spacing between the tiles on the tileset image.
	 * 
	 * @return the spacing in pixels between the tiles on the tileset image
	 */
	public int getTileSpacing() {
		return this.tileSpacing;
	}

	/**
	 * Returns the margin around the tiles on the tileset image.
	 * 
	 * @return the margin in pixels around the tiles on the tileset image
	 */
	public int getTileMargin() {
		return this.tileMargin;
	}

	/**
	 * Returns the number of tiles per row in the original tileset image.
	 * 
	 * @return the number of tiles per row in the original tileset image.
	 */
	public int getTilesPerRow() {
		return this.tilesPerRow;
	}

	/**
	 * Gets the tile with <b>local</b> id <code>i</code>.
	 * 
	 * @param i
	 *            local id of tile
	 * @return A tile with local id <code>i</code> or <code>null</code> if no tile exists with that id
	 */
	public Tile getTile(int i) {
		try {
			return this.tiles.get(i);
		} catch (ArrayIndexOutOfBoundsException a) {
		}
		return null;
	}

	/**
	 * Returns the first non-null tile in the set.
	 * 
	 * @return The first tile in this tileset, or <code>null</code> if none exists.
	 */
	public Tile getFirstTile() {
		Tile ret = null;
		int i = 0;
		while ((ret == null) && (i <= this.getMaxTileId())) {
			ret = this.getTile(i);
			i++;
		}
		return ret;
	}

	/**
	 * Returns the source of this tileset.
	 * 
	 * @return a filename if tileset is external or <code>null</code> if tileset is internal.
	 */
	public String getSource() {
		return this.externalSource;
	}

	/**
	 * Returns the base directory for the tileset
	 * 
	 * @return a directory in native format as given in the tileset file or tag
	 */
	public String getBaseDir() {
		return this.base;
	}

	//	/**
	//	 * Returns the filename of the tileset image.
	//	 * 
	//	 * @return the filename of the tileset image, or <code>null</code> if this tileset doesn't reference a tileset image
	//	 */
	//	public String getTilebmpFile() {
	//		if (this.tilebmpFile != null) {
	//			try {
	//				return this.tilebmpFile.getCanonicalPath();
	//			} catch (IOException e) {
	//			}
	//		}
	//
	//		return null;
	//	}

	/**
	 * Returns the transparent color of the tileset image, or <code>null</code> if none is set.
	 * 
	 * @return Color - The transparent color of the set
	 */
	public Color getTransparentColor() {
		return this.transparentColor;
	}

	/**
	 * @return the name of the tileset, and the total tiles
	 */
	@Override
	public String toString() {
		return this.getName() + " [" + this.size() + "]";
	}

	// TILE IMAGE CODE

	/**
	 * Returns whether the tileset is derived from a tileset image.
	 * 
	 * @return tileSetImage != null
	 */
	public boolean isSetFromImage() {
		return this.tileSetImage != null;
	}

	/**
	 * Returns the image that will be cutted once created a "tile"
	 * 
	 * @return the whole image
	 */
	public Texture getTexture() {
		return this.texture;
	}

	/**
	 * @return The image Name
	 */
	public String getImgSource() {
		return this.imgSource;
	}

	/**
	 * Sets the filename of the tileset image. Doesn't change the tileset in any other way.
	 * 
	 * @param imgSource
	 */
	public void setImgSource(String imgSource) {
		this.imgSource = imgSource;
	}

}
